package com.hls.mybatis.core;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import com.hls.mybatis.core.database.Function;
import com.hls.mybatis.core.database.MapperBean;
import com.hls.mybatis.core.database.MapperSupport;
import com.hls.mybatis.core.exception.MapperFileFoundException;

public class MyHandler implements InvocationHandler {
    
    private MapperBean mapperBean;
    
    public MapperBean getMapperBean() {
        return mapperBean;
    }

    public void setMapperBean(MapperBean mapperBean) {
        this.mapperBean = mapperBean;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        
        System.out.println("invoke method:"+method.getName());

        String sqlFilePath = method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/") + ".xml";
        System.out.println(method.getName()+" => "+sqlFilePath);
        
        int paraCount = method.getParameterCount();
        
        InputStream in=ClassLoader.getSystemResourceAsStream(sqlFilePath);
        if(in==null)
        {
            throw new MapperFileFoundException();
        }
        else 
        {
            //read xml file
            readMapperFile(new File(this.getClass().getResource("/").getPath()).getAbsolutePath()+"/"+sqlFilePath);
            if(mapperBean!=null) 
            {
                List<Function> list = mapperBean.getList();
                Object obj = null;
                for(Function func:list)
                {
                    if(func.getFuncName().equals(method.getName()))
                    {
                        //if the method is select method then invoke select method else invoke update method.
                        if(func.getSqltype().equals("select"))
                        {
                            if("java.util.ArrayList".equals(func.getResultType()))
                            {
                                Type type = method.getGenericReturnType();
                                Type paramGenericReturnType = null;
                                if (type instanceof ParameterizedType) { 
                                    Type[] typeParameteried = ((ParameterizedType) type).getActualTypeArguments();
                                    // getActualTypeArguments() can get actual type of map<String,UserInfo>
                                    // String,UserInfo may have more than one parameter so use array
                                    for (Type actualGenericType : typeParameteried) {
                                        paramGenericReturnType =actualGenericType;
                                        System.out.println(actualGenericType.getTypeName());
                                        break;
                                    }
                                }
                                if(paraCount > 0)
                                    obj = MapperSupport.selectForBeanList(Class.forName(paramGenericReturnType.getTypeName()), func.getSql(), args);
                                else obj = MapperSupport.selectForBeanList(Class.forName(paramGenericReturnType.getTypeName()), func.getSql());
                            }
                            else 
                            {
                                if(paraCount > 0)
                                    obj = MapperSupport.selectForBean(Class.forName(func.getResultType()), func.getSql(), args);
                                else obj = MapperSupport.selectForBean(Class.forName(func.getResultType()), func.getSql());
                            }
                        }
                        else
                        {
                            if(paraCount > 0)
                                obj = MapperSupport.update(func.getSql(), args);
                            else obj = MapperSupport.update(func.getSql());
                        }
                    }
                }
                return obj;
            }
        }
        return null;
    }
    
    /**
     * read mapper xml file
     * @param filePath
     */
    private void readMapperFile(String filePath)
    {
        System.out.println("mapper file path:"+filePath);
        File file = new File(filePath);
        SAXReader reader = new SAXReader();
        mapperBean = new MapperBean();
        try
        {
            Document doc = reader.read(file);
            Element root = doc.getRootElement();
            mapperBean.setInterfaceName(root.attributeValue("id").trim());
            
            List<Function> list = new ArrayList<Function>(); 
            for(Iterator<Element> rootIter = root.elementIterator();rootIter.hasNext();)
            {
                Function func = new Function();
                Element e = (Element) rootIter.next(); 
                String sqltype = e.getName().trim();
                String funcName = e.attributeValue("id").trim();
                String sql = e.getText().trim();
                String resultType = e.attributeValue("resultType").trim();
                func.setSqltype(sqltype);
                func.setFuncName(funcName);
                func.setResultType(resultType);
                func.setSql(sql);
                list.add(func);
            }
            mapperBean.setList(list);
            
        } catch (DocumentException e)
        {
            System.out.println("error occured then reading mapper xml file.");
            e.printStackTrace();
        }
    }
}
